package game.worlds;

import game.data.GameOption;
import haxe.Timer;
import hx.ui.UIManager;
import hx.geom.Point;
import echo.data.Options.ListenerOptions;
import echo.Body;
import echo.Line;
import hx.display.EchoDebug;
import hx.display.CustomDisplayObject;
import echo.util.Debug.OpenFLDebug;
import echo.data.Options.WorldOptions;
import hx.display.DisplayObject;
import hx.assets.Assets;
import hx.display.Box;
import hx.display.Scene;

/**
 * 基础的世界类型
 */
class World extends Scene {
	/**
	 * 摄像机
	 */
	public var cameraBox:Box;

	/**
	 * 世界资源
	 */
	public var assets:Assets;

	/**
	 * 世界容器，一般储存角色、特效、地图等
	 */
	public var worldBox:Box;

	/**
	 * 地图容器，一般储存地图
	 */
	public var mapBox:Box;

	/**
	 * 角色、特效渲染容器
	 */
	public var displayBox:Box;

	/**
	 * 顶部渲染容器
	 */
	public var topBox:Box;

	/**
	 * 当前场景上所有的角色
	 */
	public var roles:Array<GameRole> = [];

	/**
	 * 地图碰撞块
	 */
	public var mapDisplays:Array<MapDisplay> = [];

	/**
	 * 背景渲染对象
	 */
	public var bgDisplays:Array<BGDisplay> = [];

	/**
	 * 摄像机
	 */
	public var camera:Camera;

	/**
	 * 物理模拟世界
	 */
	public var echoWorld:echo.World;

	/**
	 * 地图的所有子碰撞块
	 */
	public var mapBoids:Array<Body> = [];

	/**
	 * 地图的所有不可穿越的碰撞块
	 */
	public var statisBoids:Array<Body> = [];

	/**
	 * 角色的所有子碰撞块
	 */
	public var roleBoids:Array<Body> = [];

	/**
	 * 技能的所有子碰撞块
	 */
	public var skillBoids:Array<Body> = [];

	/**
	 * Echo物理模拟视图
	 */
	private var __echoDebug:EchoDebug;

	/**
	 * 是否设置调试模式，当处于调试模式下，会渲染角色的碰撞区域
	 */
	public var debug(get, set):Bool;

	private function set_debug(v:Bool):Bool {
		if (v) {
			if (__echoDebug == null) {
				__echoDebug = new EchoDebug();
			}
		} else {
			if (__echoDebug != null) {
				__echoDebug.canvas.remove();
				__echoDebug = null;
			}
		}
		return v;
	}

	private function get_debug():Bool {
		return __echoDebug != null;
	}

	/**
	 * 地图宽度
	 */
	public var mapWidth:Float = 0;

	/**
	 * 地图高度
	 */
	public var mapHeight:Float = 0;

	public function new() {
		super();
	}

	/**
	 * 初始化物理世界
	 * @param option 
	 */
	public function initEchoWorld(option:WorldOptions):Void {
		echoWorld = new echo.World(option);
		// 侦听碰撞关系
		this.echoWorld.listen(roleBoids, statisBoids);
		this.echoWorld.listen(skillBoids, statisBoids);

		var option:ListenerOptions = {
			condition: (a, b, list) -> {
				// 台阶碰撞逻辑
				if (b.entity != null && b.entity.name != "unjump") {
					var role:IDisplay = cast a.entity;
					for (data in list) {
						if (data.normal.y < 0.8 || role.body.velocity.y < 0 || !role.condition(b)) {
							return false;
						}
					}
				}
				return true;
			},
			enter: (a, b, c) -> {
				var role:IDisplay = cast a.entity;
				role.enter(b);
			}
		};
		this.echoWorld.listen(roleBoids, mapBoids, option);
		this.echoWorld.listen(skillBoids, mapBoids, option);
	}

	override function onStageInit() {
		super.onStageInit();

		camera = new Camera(this);

		cameraBox = new Box();
		this.addChild(cameraBox);

		worldBox = new Box();
		cameraBox.addChild(worldBox);

		mapBox = new Box();
		worldBox.addChild(mapBox);

		displayBox = new Box();
		worldBox.addChild(displayBox);

		topBox = new Box();
		worldBox.addChild(topBox);

		this.onAssetsLoad();
		if (assets != null) {
			assets.onComplete(a -> {
				this.onGameStart();
			});
			assets.onError(err -> {
				trace("加载失败", err);
			});
			UIManager.bindAssets(assets);
			assets.start();
		}
	}

	/**
	 * 获得当前世界已运行的时间
	 * @return Float
	 */
	public var worldRunTime(get, never):Float;

	/**
	 * 当前时间已经经过了的时间
	 */
	private var __worldRunTime:Float = 0.;

	private function get_worldRunTime():Float {
		return __worldRunTime;
	}

	override function onUpdate(dt:Float) {
		super.onUpdate(dt);
		if (paused)
			return;
		while (dt > 0) {
			if (dt > GameOption.DT_FPS) {
				this.advance(GameOption.DT_FPS);
				dt -= GameOption.DT_FPS;
			} else {
				this.advance(dt);
				dt = 0;
			}
		}
	}

	public function advance(dt:Float) {
		if (debug) {
			__echoDebug.clear();
		}
		__worldRunTime += dt;
		// 角色、特效等对象更新
		for (object in this.displayBox.children.copy()) {
			object.onUpdate(dt);
		}
	}

	public function onEchoUpdate(dt:Float):Void {
		// 物理世界更新
		if (echoWorld != null) {
			echoWorld.step(dt);
			if (__echoDebug != null) {
				__echoDebug.clear();
				__echoDebug.draw(echoWorld);
				for (role in this.roles) {
					var line = Line.get(role.body.x, role.body.y - 10, role.body.x, role.body.y + 15);
					__echoDebug.draw_line(line.start.x, line.start.y, line.end.x, line.end.y, __echoDebug.intersection_color);
					__echoDebug.draw_line(line.start.x - 5, line.start.y, line.end.x - 5, line.end.y, __echoDebug.intersection_color);
					__echoDebug.draw_line(line.start.x + 5, line.start.y, line.end.x + 5, line.end.y, __echoDebug.intersection_color);
				}
			}
		}

		if (debug) {
			this.onDebugDraw();
			this.worldBox.addChild(__echoDebug.canvas);
		}
	}

	/**
	 * 添加显示对象到地图上
	 * @param role 
	 */
	public function addDisplay(display:IDisplay):Void {
		var isRole = false;
		if (display is GameRole) {
			var role:GameRole = cast display;
			roles.push(role);
			role.updateLastPoint();
			isRole = true;
		}
		if (display.body != null && display.body.world == null) {
			if (isRole)
				this.roleBoids.push(display.body);
			this.echoWorld.add(display.body);
		}
		displayBox.addChild(cast display);
	}

	/**
	 * 根据昵称获得特效显示对象
	 * @param name 
	 * @param display 是否指定某个角色释放的特效
	 * @return DisplayObject
	 */
	public function getGameEffectByName(name:String, display:IDisplay = null):GameEffect {
		for (object in this.displayBox.children) {
			if (object.name == name && object is GameEffect && (display == null || cast(object, GameEffect).display == display)) {
				return cast object;
			}
		}
		return null;
	}

	/**
	 * 根据昵称获得特效显示对象
	 * @param name 
	 * @param role 是否指定某个角色释放的特效
	 * @return DisplayObject
	 */
	public function getAllGameEffectByName(name:String, role:GameRole = null):GameEffect {
		for (object in this.displayBox.children) {
			if (object.name == name && object is GameEffect && (role == null || cast(object, GameEffect).role == role)) {
				return cast object;
			}
		}
		return null;
	}

	/**
	 * 移除显示对象
	 * @param role 
	 */
	public function removeDisplay(display:IDisplay):Void {
		var isRole = false;
		if (display is GameRole) {
			var role:GameRole = cast display;
			roles.remove(role);
			isRole = true;
		}
		if (display.body != null && display.body.world == null) {
			if (isRole)
				this.roleBoids.remove(display.body);
			this.echoWorld.remove(display.body);
		}
		displayBox.removeChild(cast display);
	}

	/**
	 * 添加地图显示对象
	 * @param display 
	 */
	public function addMapDisplay(display:IDisplay):Void {
		if (mapBoids.indexOf(display.body) == -1)
			this.mapBoids.push(display.body);
		if (display.body.world == null) {
			this.echoWorld.add(display.body);
		}
		mapBox.addChild(cast display);
	}

	/**
	 * 移除地图显示对象
	 * @param display 
	 */
	public function removeMapDisplay(display:IDisplay):Void {
		if (mapBoids.indexOf(display.body) == -1)
			this.mapBoids.remove(display.body);
		if (display.body != null && display.body.world != null) {
			this.echoWorld.remove(display.body);
		}
		mapBox.removeChild(cast display);
	}

	/**
	 * 测试地图射线
	 * @param v 
	 */
	public function testMapRay(start:Point, end:Point):echo.data.Data.Intersection {
		var line = Line.get(start.x, start.y, end.x, end.y);
		var testList = this.mapBoids;
		var ret = line.linecast(testList, this.echoWorld);
		return ret;
	}

	/**
	 * 绘制调试信息
	 */
	public function onDebugDraw() {}

	/**
	 * 资源加载统一入口实现，请在此方法通过`assets`加载资源
	 */
	public function onAssetsLoad():Void {}

	/**
	 * 当游戏可以开始时触发此事件，例如`assets`资源加载完成时，触发此事件
	 */
	public function onGameStart():Void {
		this.__worldRunTime = 0;
		this.updateEnabled = true;
	}

	public function onAssetsError(code:Int, message:String):Void {
		trace('assets error: $code $message');
	}

	/**
	 * 是否暂停游戏
	 */
	public var paused:Bool = false;

	public function pauseGame():Void {
		this.paused = true;
	}

	/**
	 * 继续游戏
	 */
	public function continueGame():Void {
		this.paused = false;
	}

	/**
	 * 调用程序重新开始，此方法会清空地图上的所有显示对象，`onGameStart`会重新出发此调用逻辑
	 */
	public function restart():Void {
		for (role in this.roles) {
			role.clean();
		}
		for (display in this.mapDisplays) {
			display.clean();
		}
		this.topBox.removeChildren();
		this.displayBox.removeChildren();
		this.roles = [];
		this.mapDisplays = [];
		this.roleBoids = [];
		this.mapBoids = [];
		this.statisBoids = [];
		this.skillBoids = [];
		for (display in this.bgDisplays) {
			display.remove();
		}
		this.bgDisplays = [];
		this.onGameStart();
	}
}
